import { Map } from '../Map';
import { Handler } from '../../core/Handler';
import { on, off, stop } from '../../dom/DomEvent';
import { toPoint } from '../../geometry/Point';

/*
 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
 */

// @namespace Map
// @section Keyboard Navigation Options
Map.mergeOptions({
  // @option keyboard: Boolean = true
  // Makes the map focusable and allows users to navigate the map with keyboard
  // arrows and `+`/`-` keys.
  keyboard: true,

  // @option keyboardPanDelta: Number = 80
  // Amount of pixels to pan when pressing an arrow key.
  keyboardPanDelta: 80,
});

export var Keyboard = Handler.extend({
  keyCodes: {
    left: [37],
    right: [39],
    down: [40],
    up: [38],
    zoomIn: [187, 107, 61, 171],
    zoomOut: [189, 109, 54, 173],
  },

  initialize: function (map) {
    this._map = map;

    this._setPanDelta(map.options.keyboardPanDelta);
    this._setZoomDelta(map.options.zoomDelta);
  },

  addHooks: function () {
    var container = this._map._container;

    // make the container focusable by tabbing
    if (container.tabIndex <= 0) {
      container.tabIndex = '0';
    }

    on(
      container,
      {
        focus: this._onFocus,
        blur: this._onBlur,
        mousedown: this._onMouseDown,
      },
      this,
    );

    this._map.on(
      {
        focus: this._addHooks,
        blur: this._removeHooks,
      },
      this,
    );
  },

  removeHooks: function () {
    this._removeHooks();

    off(
      this._map._container,
      {
        focus: this._onFocus,
        blur: this._onBlur,
        mousedown: this._onMouseDown,
      },
      this,
    );

    this._map.off(
      {
        focus: this._addHooks,
        blur: this._removeHooks,
      },
      this,
    );
  },

  _onMouseDown: function () {
    if (this._focused) {
      return;
    }

    var body = document.body,
      docEl = document.documentElement,
      top = body.scrollTop || docEl.scrollTop,
      left = body.scrollLeft || docEl.scrollLeft;

    this._map._container.focus();

    window.scrollTo(left, top);
  },

  _onFocus: function () {
    this._focused = true;
    this._map.fire('focus');
  },

  _onBlur: function () {
    this._focused = false;
    this._map.fire('blur');
  },

  _setPanDelta: function (panDelta) {
    var keys = (this._panKeys = {}),
      codes = this.keyCodes,
      i,
      len;

    for (i = 0, len = codes.left.length; i < len; i++) {
      keys[codes.left[i]] = [-1 * panDelta, 0];
    }
    for (i = 0, len = codes.right.length; i < len; i++) {
      keys[codes.right[i]] = [panDelta, 0];
    }
    for (i = 0, len = codes.down.length; i < len; i++) {
      keys[codes.down[i]] = [0, panDelta];
    }
    for (i = 0, len = codes.up.length; i < len; i++) {
      keys[codes.up[i]] = [0, -1 * panDelta];
    }
  },

  _setZoomDelta: function (zoomDelta) {
    var keys = (this._zoomKeys = {}),
      codes = this.keyCodes,
      i,
      len;

    for (i = 0, len = codes.zoomIn.length; i < len; i++) {
      keys[codes.zoomIn[i]] = zoomDelta;
    }
    for (i = 0, len = codes.zoomOut.length; i < len; i++) {
      keys[codes.zoomOut[i]] = -zoomDelta;
    }
  },

  _addHooks: function () {
    on(document, 'keydown', this._onKeyDown, this);
  },

  _removeHooks: function () {
    off(document, 'keydown', this._onKeyDown, this);
  },

  _onKeyDown: function (e) {
    if (e.altKey || e.ctrlKey || e.metaKey) {
      return;
    }

    var key = e.keyCode,
      map = this._map,
      offset;

    if (key in this._panKeys) {
      if (!map._panAnim || !map._panAnim._inProgress) {
        offset = this._panKeys[key];
        if (e.shiftKey) {
          offset = toPoint(offset).multiplyBy(3);
        }

        if (map.options.maxBounds) {
          offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
        }

        if (map.options.worldCopyJump) {
          var newLatLng = map.wrapLatLng(
            map.unproject(map.project(map.getCenter()).add(offset)),
          );
          map.panTo(newLatLng);
        } else {
          map.panBy(offset);
        }
      }
    } else if (key in this._zoomKeys) {
      map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
    } else if (
      key === 27 &&
      map._popup &&
      map._popup.options.closeOnEscapeKey
    ) {
      map.closePopup();
    } else {
      return;
    }

    stop(e);
  },
});

// @section Handlers
// @section Handlers
// @property keyboard: Handler
// Keyboard navigation handler.
Map.addInitHook('addHandler', 'keyboard', Keyboard);
